package com.aaa.bitmapcache;

import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.util.Log;
import android.util.LruCache;

import com.aaa.bitmapcache.disk.DiskLruCache;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class ImageCache {
    private static final String TAG = "ImageCache";
    private static ImageCache instance;

    /**
     * 单例类
     *
     * @return
     */
    public static ImageCache getInstance() {
        if (instance == null) {
            synchronized (ImageCache.class) {
                if (instance == null) {
                    instance = new ImageCache();
                }
            }
        }
        return instance;
    }

    private LruCache<String, Bitmap> lruCache; // 内存缓存 缓存的是一个个Bitmap对象
    private Set<WeakReference<Bitmap>> reusablePool; // 复用池 复用的是一块块内存空间
    private Set<SoftReference<Bitmap>> reusableBitmaps; // 复用池 复用的是一块块内存空间
    /*
当lruCache中某个对象不用的时候，把其内存放入reusablePool，
当需要新建一个Bitmap时就从reusablePool中取出这块内存使用，避免频繁回收和申请开辟内存造成的内存抖动及OOM
    * */
    private DiskLruCache diskLruCache; // 磁盘缓存

    public void init(Context context, String dir) {

        // 初始化复用池
        reusablePool = Collections.synchronizedSet(new HashSet<WeakReference<Bitmap>>());
        reusableBitmaps = Collections.synchronizedSet(new HashSet<SoftReference<Bitmap>>());

        // App可使用的总内存，单位M
        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        int memoryClass = am.getMemoryClass();
        // memoryClass * 1024 * 1024 / 8 ，常以总内存的1/8作为内存缓存
        // 本例一张图片占 2155200，以10张作为内存缓存的大小
//        lruCache = new LruCache<String, Bitmap>(memoryClass * 1024 * 1024 / 8) {
        // 初始化内存缓存
        // LruCache缓存大小没生效？？？是因为缓存太大，未被填满所致
        lruCache = new LruCache<String, Bitmap>(16950 * 12) {
            // 返回的一张图片大小
            @Override
            protected int sizeOf(String key, Bitmap value) {
                Log.i(TAG, "sizeOf: " + value.getAllocationByteCount()); // 38420 // 16950
                if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
                    // getAllocationByteCount()大于等于getByteCount()，更准确，但4.4+之后添加
                    return value.getAllocationByteCount();
                }
                return value.getByteCount();
            }

            @Override
            protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
                // 易变才能复用，添加到复用池，否则直接回收
                if (oldValue.isMutable()) {
                    /*
3.0 bitmap 缓存到 native 层
3.0 ~ 8.0  bitmap 缓存到 java 层
8.0 bitmap 又缓存到 native 层
区别是缓存到 java 层，GC时会自动回收，不用手动调用recycle()，
而缓存到 native 层，则需要手动调用recycle()去进行回收
                    * */
                    Log.i(TAG, "entryRemoved: ");
                    // 为何使用引用队列？
                    /*
因为复用池中的Bitmap使用的是弱引用，被GC扫到就被回收无法执行recycle()，
考虑到8.0之后Bitmap缓存到native层，必须recycle(),
所以使用引用队列，其作用是，GC扫过后，弱引用会被放到引用队列中，仍可执行recycle()，回收native层缓存的Bitmap
                    * */
                    reusablePool.add(new WeakReference<Bitmap>(oldValue, getReferenceQueue()));
                    reusableBitmaps.add(new SoftReference<Bitmap>(oldValue));
                } else {
                    oldValue.recycle();
                }
            }
        };

        // 初始化磁盘缓存
        try {
//            diskLruCache = DiskLruCache.open(new File(dir), BuildConfig.VERSION_CODE, 1, 10 * 1024 * 1024);
            diskLruCache = DiskLruCache.open(new File(dir), BuildConfig.VERSION_CODE, 1, 9 * 2155200);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 放入磁盘缓存
     */
    public void putBitmap2Disk(String key, Bitmap bitmap) {
        DiskLruCache.Snapshot snapshot = null;
        OutputStream os = null;
        try {
            snapshot = diskLruCache.get(key);
            if (snapshot == null) {
                DiskLruCache.Editor edit = diskLruCache.edit(key);
                if (edit != null) {
                    os = edit.newOutputStream(0);
                    bitmap.compress(Bitmap.CompressFormat.JPEG, 50, os);
                    edit.commit();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NullPointerException e) {
            e.printStackTrace();
        } finally {
            if (snapshot != null) {
                snapshot.close();
            }
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 从磁盘缓存获取bitmap
     *
     * @param key
     * @param reusable
     * @return
     */
    public Bitmap getBitmapFromDisk(String key, Bitmap reusable) {
        DiskLruCache.Snapshot snapshot = null;
        Bitmap bitmap = null;
        try {
            snapshot = diskLruCache.get(key);
            if (snapshot == null) {
                return null;
            }
            InputStream is = snapshot.getInputStream(0);

            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inMutable = true;
            options.inBitmap = reusable;
            bitmap = BitmapFactory.decodeStream(is, null, options);
            if (bitmap != null) {
                lruCache.put(key, bitmap);
            }

        } catch (IOException e) {
            e.printStackTrace();
        } catch (NullPointerException e) {
            e.printStackTrace();
        } finally {
            if (snapshot != null) {
                snapshot.close();
            }
        }
        return bitmap;
    }

    private ReferenceQueue<Bitmap> referenceQueue;
    boolean shutDown;

    private ReferenceQueue<Bitmap> getReferenceQueue() {
        if (referenceQueue == null) {
            referenceQueue = new ReferenceQueue<>();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (!shutDown) {
                        try {
                            // TODO: 2019/8/29 几乎无效 
                            // remove()是阻塞的
                            Reference<? extends Bitmap> remove = referenceQueue.remove();
                            Log.i(TAG, "run: ..." + remove);
                            Bitmap bitmap = remove.get();
                            if (bitmap != null && !bitmap.isRecycled()) {
                                Log.i(TAG, "run: ....");
                                bitmap.recycle();
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }
        return referenceQueue;
    }

    /**
     * 把bitmap 放入内存
     *
     * @param key
     * @param bitmap
     */
    public void putBitmap2Memory(String key, Bitmap bitmap) {
        lruCache.put(key, bitmap);
    }

    /**
     * 从内存中取出bitmap
     *
     * @param key
     * @return
     */
    public Bitmap getBitmapFromMemory(String key) {
        return lruCache.get(key);
    }

    public void clearMemory() {
        lruCache.evictAll();
    }


    /**
     * 3.0 之前不能复用
     * 3.0-4.4 宽高一样，inSampleSize = 1 才能复用
     * 4.4 只要小于等于就能复用
     *
     * @param w            所需内存空间的Bitmap的BitmapWidth
     * @param h            所需内存空间的Bitmap的BitmapHeight
     * @param inSampleSize 所需内存空间的Bitmap的采样率
     * @param config       所需内存空间的Bitmap的像素点格式
     * @return
     */
    public Bitmap getReusable(int w, int h, int inSampleSize, Bitmap.Config config) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
            return null;
        }
        Bitmap reusable = null;
        Log.i(TAG, "getReusable: 复用池大小" + reusablePool.size());
        // 遍历复用池找到满足大小的内存空间
        Iterator<WeakReference<Bitmap>> iterator = reusablePool.iterator();
        while (iterator.hasNext()) {
            Bitmap bitmap = iterator.next().get();
            if (bitmap != null) {
                Log.i(TAG, "getReusable: 复用池不为null " + bitmap);
                if (checkInBitmap(bitmap, w, h, inSampleSize, config)) {
                    reusable = bitmap;
                    // 复用池中有可以复用的，则让其复用并从复用池移除
                    iterator.remove();
                    break;
                }
            } else {
                // 已被回收不能复用的则直接移除
                iterator.remove();
            }
        }
        return reusable;
    }

    // This method iterates through the reusable bitmaps, looking for one to use for inBitmap:
    protected Bitmap getBitmapFromReusableSet(BitmapFactory.Options options, Bitmap.Config targetConfig) {
        Bitmap bitmap = null;
        if (reusableBitmaps != null && !reusableBitmaps.isEmpty()) {
            synchronized (reusableBitmaps) {
                final Iterator<SoftReference<Bitmap>> iterator = reusableBitmaps.iterator();
                Bitmap item;
                while (iterator.hasNext()) {
                    item = iterator.next().get();
                    if (null != item && item.isMutable()) {
                        // Check to see it the item can be used for inBitmap.
                        if (canUseForInBitmap(item, options, targetConfig)) {
                            bitmap = item;
                            // Remove from reusable set so it can't be used again.
                            iterator.remove();
                            break;
                        }
                    } else {
                        // Remove from the set if the reference has been cleared.
                        iterator.remove();
                    }
                }
            }
        }
        return bitmap;
    }

    static boolean canUseForInBitmap(Bitmap candidate, BitmapFactory.Options targetOptions, Bitmap.Config targetConfig) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            // From Android 4.4 (KitKat) onward we can re-use if the byte size of
            // the new bitmap is smaller than the reusable bitmap candidate
            // allocation byte count.
            int width = targetOptions.outWidth / targetOptions.inSampleSize;
            int height = targetOptions.outHeight / targetOptions.inSampleSize;
//            int byteCount = width * height * getBytesPerPixel(candidate.getConfig());
            int byteCount = width * height * getBytesPerPixel(targetConfig);
            return byteCount <= candidate.getAllocationByteCount();
        }
        // On earlier versions, the dimensions must match exactly and the inSampleSize must be 1
        return candidate.getWidth() == targetOptions.outWidth
                && candidate.getHeight() == targetOptions.outHeight
                && targetOptions.inSampleSize == 1;
    }

    /**
     * 校验bitmap 是否满足条件
     *
     * @param bitmap       复用池里已分配的内存空间
     * @param w            所需内存空间的Bitmap的BitmapWidth
     * @param h            所需内存空间的Bitmap的BitmapHeight
     * @param inSampleSize 所需内存空间的Bitmap的采样率
     * @param config       所需内存空间的Bitmap的像素点格式
     * @return
     */
    private boolean checkInBitmap(Bitmap bitmap, int w, int h, int inSampleSize, Bitmap.Config config) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
            return bitmap.getWidth() == w && bitmap.getHeight() == h && inSampleSize == 1;
        }
        if (inSampleSize > 1) {
            w /= inSampleSize;
            h /= inSampleSize;
        }
        int byteCount = w * h * getBytesPerPixel(config);
        // 图片内存 系统分配内存
        return byteCount <= bitmap.getAllocationByteCount();
    }

    /**
     * 通过像素格式计算每一个像素占用多少字节
     * A helper function to return the byte usage per pixel of a bitmap based on its configuration.
     */
    static int getBytesPerPixel(Bitmap.Config config) {
        if (config == Bitmap.Config.ARGB_8888) {
            return 4;
        } else if (config == Bitmap.Config.RGB_565) {
            return 2;
        } else if (config == Bitmap.Config.ARGB_4444) {
            return 2;
        } else if (config == Bitmap.Config.ALPHA_8) {
            return 1;
        }
        return 1;
    }

}
